package com.db.httpserver;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;

public class NioHttpServer {

    private static volatile boolean running = true;
    private static ByteBuffer buffer = ByteBuffer.allocate(16384);
    private static Selector selector;

    public static void main(String[] args) throws IOException {
        Parameter.setPort(args.length > 0 ? Integer.parseInt(args[0]) : -1);
        Parameter.print();
        int port = Parameter.getPort();

        // Instead of creating a ServerSocket,
        // create a ServerSocketChannel
        ServerSocketChannel ssc = ServerSocketChannel.open();

        // Set it to non-blocking, so we can use select
        ssc.configureBlocking(false);

        // Get the Socket connected to this channel, and bind it
        // to the listening port
        ServerSocket ss = ssc.socket();
        InetSocketAddress isa = new InetSocketAddress(port);
        ss.bind(isa);

        // Create a new Selector for selecting
        selector = Selector.open();

        // Register the ServerSocketChannel, so we can
        // listen for incoming connections
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (running) {
            if (selector.select() == 0) {
                continue;
            }
            final Set<SelectionKey> keys = selector.selectedKeys();
            for (Iterator<SelectionKey> it = keys.iterator(); it.hasNext();) {
                final SelectionKey key = it.next();
                it.remove();
                if (key.isAcceptable()) {
                    System.out.println("__acceptible");
                    // It's an incoming connection.
                    // Register this socket with the Selector
                    // so we can listen for input on it

                    Socket s = ss.accept();
                    System.out.println( "Got connection from "+s );

                    // Make sure to make it non-blocking, so we can
                    // use a selector on it.
                    SocketChannel sc = s.getChannel();
                    sc.configureBlocking( false );

                    // Register it with the selector, for reading
                    sc.register( selector, SelectionKey.OP_READ );
                } else if (key.isReadable()) {
                    SocketChannel sc = null;
                    try {
                        // It's incoming data on a connection, so
                        // process it
                        sc = (SocketChannel) key.channel();
                        boolean ok = processInput(sc);
                        System.out.println("ok");
                        // If the connection is dead, then remove it
                        // from the selector and close it
                        if (!ok) {
                            System.out.println("!ok");
                            //key.cancel();

                            Socket s = null;
                            try {
                                s = sc.socket();
                                s.close();
                            } catch (IOException ie) {
                                System.err.println("Error closing socket " + s
                                        + ": " + ie);
                            }
                        }
                    } catch (IOException ie) {
                        // On exception, remove this channel from the
                        // selector
                        //key.cancel();
                        try {
                            sc.close();
                        } catch (IOException ie2) {
                            System.out.println(ie2);
                        }
                        System.out.println("Closed " + sc);
                    }
                /*} else if (key.isWritable()) {
                    System.out.println("__Writeble");
                    try {
                        processOutput(key);
                    } catch (IOException e) {
                        e.printStackTrace();
                        //key.cancel();
                        key.channel().close();
                    }*/
                }
                key.channel().close();
            }
        }

    }

    private static boolean processInput(SocketChannel sc) throws IOException {
        byte[] buf = new byte[0];
        int bytesRead = sc.read(buffer);
        while (bytesRead > 0) {
            buffer.flip();
            /*
             * // If no data, close the connection if (buffer.limit() == 0) {
             * return false; }
             */

            buf = Arrays.copyOf(buf, buf.length + bytesRead);
            System.arraycopy(buffer.array(), 0, buf, buf.length - bytesRead,
                    bytesRead);

            buffer.clear();
            bytesRead = sc.read(buffer);
            System.out.println("read");
        }

        for (int i = 0; i < buf.length; ++i) {
            System.out.print((char) buf[i]);
        }

        final Request request = new RequestScanner(
                new ByteArrayInputStream(buf)).scan();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(baos);

        new Worker().handleRequest(request, ps);

        //SelectionKey sk = sc.register(selector, SelectionKey.OP_WRITE);
        //sk.attach(baos.toByteArray());

        System.out.println("=========");
        for (byte b : baos.toByteArray()) {
            System.out.print((char) b);
        }

        ByteBuffer output = ByteBuffer.wrap(baos.toByteArray());
        if (!output.hasRemaining()) {
            output.rewind();
        }

        sc.write(output);
        
        return true;
    }

    private static void processOutput(SelectionKey key) throws IOException {

        ByteBuffer output = ByteBuffer.wrap((byte[]) key.attachment());
        if (!output.hasRemaining()) {
            output.rewind();
        }

        ((SocketChannel) key.channel()).write(output);
        System.out.println("proccessed output");
    }
}
